import { Injectable, BadRequestException } from '@nestjs/common';
import { TypeOrmCrudService } from '@nestjsx/crud-typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Song, ResourceFile } from '../entities';
import { CrudRequest } from '@nestjsx/crud';
import {
  DeepPartial,
  Repository,
  Transaction,
  TransactionRepository,
} from 'typeorm';
import { FileUtils } from '@/utils';
import { FileStoragePath } from '@/enum';
import { plainToClass } from 'class-transformer';
import { AudioUtils } from '@/utils';
import * as path from 'path';
import * as _ from 'lodash';
import { UsersService } from '@/modules/users';
@Injectable()
export class SongsService extends TypeOrmCrudService<Song> {
  constructor(
    @InjectRepository(Song) repo,
    @InjectRepository(ResourceFile)
    private readonly fileRepo: Repository<ResourceFile>,
    private readonly userService: UsersService,
  ) {
    super(repo);
  }

  async checkFav(songId: number, userId: number) {
    const song = await this.repo.findOneOrFail(songId, {
      relations: ['favByUsers'],
    });
    const index = song.favByUsers.findIndex(user => user.id === userId);
    return index !== -1;
  }

  @Transaction()
  async setFav(
    songId: number,
    userId: number,
    @TransactionRepository(Song)
    songTranRepo?: Repository<Song>,
  ) {
    const song = await songTranRepo.findOneOrFail(songId, {
      relations: ['favByUsers'],
    });
    const favdByUser = song.favByUsers.findIndex(user => user.id === userId);

    const data = _.pick(song, ['id', 'favByUsers', 'favCount']);

    if (favdByUser === -1) {
      const user = await this.userService.findOne(userId);
      data.favByUsers.push(user);
      data.favCount++;
      await songTranRepo.save(data);
    }

    return _.pick(data, ['id', 'favCount']);
  }

  @Transaction()
  async removeFav(
    songId: number,
    userId: number,
    @TransactionRepository(Song)
    songTranRepo?: Repository<Song>,
  ) {
    const song = await songTranRepo.findOneOrFail(songId, {
      relations: ['favByUsers'],
    });
    const favdByUser = song.favByUsers.findIndex(user => user.id === userId);

    const data = _.pick(song, ['id', 'favByUsers', 'favCount']);

    if (favdByUser !== -1) {
      data.favByUsers.splice(favdByUser, 1);
      data.favCount--;
      await songTranRepo.save(data);
    }

    return _.pick(data, ['id', 'favCount']);
  }

  async createOne(req: CrudRequest, dto: DeepPartial<Song>): Promise<Song> {
    if (dto.audioFile) {
      const metadata = await this.saveAudioFile(dto.audioFile);
      dto.duration = metadata.format.duration;
    }
    if (dto.mv) {
      await this.saveFile(dto.mv);
    }
    return super.createOne(req, dto);
  }

  async updateOne(req: CrudRequest, dto: DeepPartial<Song>): Promise<Song> {
    if (dto.audioFile && !dto.audioFile.id) {
      const metadata = await this.saveAudioFile(dto.audioFile);
      dto.duration = metadata.format.duration;
    }
    if (dto.mv && !dto.mv.id) {
      await this.saveFile(dto.mv);
    }
    return super.updateOne(req, dto);
  }

  async replaceOne(req: CrudRequest, dto: DeepPartial<Song>): Promise<Song> {
    if (dto.audioFile && !dto.audioFile.id) {
      const metadata = await this.saveAudioFile(dto.audioFile);
      dto.duration = metadata.format.duration;
    }
    if (dto.mv && !dto.mv.id) {
      await this.saveFile(dto.mv);
    }
    return super.replaceOne(req, dto);
  }

  /**
   * Soft delete one entity.
   *
   * Override from the `Crud` `deleteOne` function to soft delete.
   * @param req `CrudRequest` from `Crud`
   */
  async deleteOne(req: CrudRequest): Promise<void | Song> {
    const { returnDeleted } = req.options.routes.deleteOneBase;
    const found = await this.getOneOrFail(req, returnDeleted);
    const toReturn = returnDeleted
      ? plainToClass(this.entityType, { ...found })
      : undefined;
    await this.repo.softRemove(found);
    return toReturn;
  }

  private async saveAudioFile(file: DeepPartial<ResourceFile>) {
    const audio = await this.saveFile(file);
    return await AudioUtils.getMetadata(
      path.resolve(process.cwd(), audio.path),
    );
  }

  private async saveFile(file: DeepPartial<ResourceFile>) {
    file.sha256 = await FileUtils.getHashOfFile(file.path);
    let path = '';
    switch (FileUtils.getTypeFromMimetype(file.mimetype)) {
      case 'image':
        path = FileStoragePath.LIBRARY_IMAGES;
        break;
      case 'audio':
        path = FileStoragePath.LIBRARY_AUDIOS;
        break;
      case 'video':
        path = FileStoragePath.LIBRARY_VIDEOS;
        break;
      default:
        throw new BadRequestException(
          `Unexpected file type "${file.mimetype}" of file ${file.originalname}`,
        );
    }
    path += file.filename;
    await FileUtils.moveFile(file.path, path);
    file.path = path;
    return await this.fileRepo.save(file);
  }
}
